package com.xbongbong.paas.manager.impl;

import com.alibaba.dingtalk.openapi.exception.OApiException;
import com.alibaba.dingtalk.openapi.helper.AuthHelper;
import com.alibaba.dingtalk.openapi.helper.Env;
import com.alibaba.dingtalk.openapi.helper.UserHelper;
import com.alibaba.dingtalk.openapi.vo.DingtalkConfigVO;
import com.alibaba.fastjson.JSONObject;
import com.xbongbong.paas.constant.ConfigConstant;
import com.xbongbong.paas.constant.MessageConstant;
import com.xbongbong.paas.constant.TimeConstant;
import com.xbongbong.paas.enums.PlatFormEnum;
import com.xbongbong.paas.help.CommonHelp;
import com.alibaba.dingtalk.openapi.helper.RestTemplateHelper;
import com.xbongbong.paas.log.entity.LoginLogEntity;
import com.xbongbong.paas.log.model.LoginLogModel;
import com.xbongbong.paas.manager.DingtalkManager;
import com.xbongbong.paas.pojo.dto.DingtalkConfigGetDTO;
import com.xbongbong.paas.pojo.vo.DingtalkConfigGetVO;
import com.xbongbong.paas.redis.PaasRedisHelper;
import com.xbongbong.paas.redis.RedisPrefixConstant;
import com.xbongbong.paas.redis.RedisTimeConstant;
import com.xbongbong.paas.service.CompanyService;
import com.xbongbong.paas.toolbox.exception.XbbException;
import com.xbongbong.paas.toolbox.util.BeanUtil;
import com.xbongbong.paas.toolbox.util.DateTimeUtil;
import com.xbongbong.paas.toolbox.util.DesUtil;
import com.xbongbong.paas.toolbox.util.DigestUtil;
import com.xbongbong.paas.toolbox.util.JsonHelperUtil;
import com.xbongbong.paas.toolbox.util.MD5Util;
import com.xbongbong.paas.toolbox.util.StringUtil;
import com.xbongbong.paas.toolbox.util.URLEncodeUtils;
import com.xbongbong.paas.toolbox.wrap.UserVO;
import com.xbongbong.pro.config.ProBaseConfig;
import com.xbongbong.pro.dingtalk.pojo.dto.CoolAppAccessStatusDTO;
import com.xbongbong.pro.dingtalk.pojo.dto.DingtalkBeforeConfigDTO;
import com.xbongbong.pro.dingtalk.pojo.dto.DingtalkLoginInfoDTO;
import com.xbongbong.pro.dingtalk.pojo.dto.GenerateNonceDTO;
import com.xbongbong.pro.dingtalk.pojo.vo.CoolAppAccessStatusVO;
import com.xbongbong.pro.dingtalk.pojo.vo.DingtalkBeforeConfigVO;
import com.xbongbong.pro.dingtalk.pojo.vo.DingtalkLoginInfoVO;
import com.xbongbong.pro.dingtalk.pojo.vo.GenerateNonceVO;
import com.xbongbong.pro.enums.errorcodes.SystemErrorCodeEnum;
import com.xbongbong.pro.packageInfo.pojo.vo.CheckPackageVO;
import com.xbongbong.saas.constant.BasicConstant;
import com.xbongbong.saas.constant.StringConstant;
import com.xbongbong.saas.enums.CompanyConfigEnum;
import com.xbongbong.sys.domain.entity.CompanyConfigEntity;
import com.xbongbong.sys.domain.entity.CompanyEntity;
import com.xbongbong.sys.domain.entity.UserConfigEntity;
import com.xbongbong.sys.domain.entity.UserEntity;
import com.xbongbong.sys.enums.UserConfigEnum;
import com.xbongbong.sys.model.CompanyConfigModel;
import com.xbongbong.sys.model.CompanyModel;
import com.xbongbong.sys.model.UserConfigModel;
import com.xbongbong.sys.model.UserModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * @author kaka
 * @time 2018-10-16 19:50
 */
@Service("dingtalkManager")
public class DingtalkManagerImpl implements DingtalkManager {

    private static  final Logger LOG = LoggerFactory.getLogger(DingtalkManagerImpl.class);

    @Resource
    private AuthHelper authHelper;
    @Resource
    private PaasRedisHelper paasRedisHelper;
    @Resource
    private ProBaseConfig proBaseConfig;
    @Resource
    private CompanyService companyService;
    @Resource
    private UserModel userModel;
    @Resource
    private LoginLogModel loginLogModel;
    @Resource
    private UserConfigModel userConfigModel;
    @Resource
    private CompanyModel companyModel;
    @Resource
    private CommonHelp commonHelp;
    @Resource
    private RestTemplateHelper restTemplateHelper;
    @Resource
    private CompanyConfigModel companyConfigModel;

    @Override
    public DingtalkConfigGetVO getConfig(HttpServletRequest request, DingtalkConfigGetDTO configGetDTO) throws XbbException {
        DingtalkConfigGetVO configGetVO = new DingtalkConfigGetVO();
        String pageUrl = configGetDTO.getPageUrl();
        DingtalkConfigVO configVO = authHelper.getConfig(request, pageUrl);

        if (configVO == null) {
            throw new XbbException(SystemErrorCodeEnum.API_ERROR_100001.getCode(), MessageConstant.GET_DINGTALK_CONFIG_FAIL);
        }

        configGetVO.setConfig(configVO);
        return configGetVO;
    }

    @Override
    public DingtalkLoginInfoVO loginInfo(HttpServletRequest request, HttpServletResponse response, DingtalkLoginInfoDTO loginInfoDTO) throws XbbException {
        DingtalkLoginInfoVO loginInfoVO = new DingtalkLoginInfoVO();
        String corpid = loginInfoDTO.getCorpid();
        String code = loginInfoDTO.getCode();
        String scenes = loginInfoDTO.getScenes();
        String userId = null;
        if ("dingtalk".equals(scenes) || "dingtalkPc".equals(scenes)) {
            // 保险措施，填补缓存中的公司持久码
            companyService.getPermanentCode(corpid);

            userId = getUserIdFromDD(corpid, code);

        } else if("dingtalkAdmin".equals(scenes)) {
            Map<String, String> corpidAndUserId = getAdminUserFromDD(code);
            corpid = corpidAndUserId.get("corpid");
            userId = corpidAndUserId.get("userId");
        }
        /*
         * 从数据库中获取user
         * 若未null则说明该用户需要通过钉钉接口获取，并保存到我们系统的数据库中
         */
        UserEntity user = userModel.getByKeyIngoreDel(userId, corpid);
        if(user == null) {
            throw new XbbException(SystemErrorCodeEnum.API_ERROR_100025);
        }

        //置入随机数,加强登录安全性
        String nr = MD5Util.EncoderByMd5(StringUtil.getRandomPassword(4,6));
        paasRedisHelper.setValuePermanent(RedisPrefixConstant.USER_INFO_DIGEST, user.getDingId(), nr);

        // 计算token
        String userId_corpId = userId + "____" + corpid;
        String xbbAccessToken = paasRedisHelper.getValue(RedisPrefixConstant.XBB_ACCESS_TOKEN, userId_corpId);
        //如果不存在accessToken，再去生成
        if(StringUtil.isEmpty(xbbAccessToken)){
            //生成接口的accessToken,存cookie
            xbbAccessToken = RedisPrefixConstant.XBB_ACCESS_TOKEN + System.currentTimeMillis() + userId_corpId;
            xbbAccessToken = DigestUtil.Encrypt(xbbAccessToken, "SHA-256");
        }
        //过期时间，默认一天
        paasRedisHelper.setValue(RedisPrefixConstant.XBB_ACCESS_TOKEN, userId_corpId, xbbAccessToken, TimeConstant.SECONDS_PER_DAY);

        // 清除网关UserVO缓存, 防止权限/员工/部门变更无法生效
        String loginUserKey = corpid + "____" + userId;
        paasRedisHelper.removeValue(RedisPrefixConstant.PAAS_LOGIN_USER_INFO, loginUserKey);

        UserVO userVO = new UserVO();
        BeanUtil.copyProperties(user, userVO);

        //登录日志
        LoginLogEntity loginLog = new LoginLogEntity(corpid, userId, user.getName(), scenes, request);
        loginLogModel.insert(loginLog);

        loginInfoVO.setXbbAccessToken(xbbAccessToken);
        loginInfoVO.setUser(userVO);
        loginInfoVO.setNr(nr);

        //套餐限制
        CheckPackageVO checkPackageVO = companyService.checkPackage(corpid);

        if (Objects.equals(checkPackageVO.getIsActive(), BasicConstant.ZERO)) {
            // 超过套餐类型
            JSONObject packageInfo = companyService.handlePackageErrorInfo(user, checkPackageVO.getExpireType());

            loginInfoVO.setCode(SystemErrorCodeEnum.API_ERROR_100070.getCode());
            loginInfoVO.setMsg(checkPackageVO.getShowMsg());
            loginInfoVO.setPackageErrorInfo(packageInfo);

            // 获取钉钉服务群信息
            CompanyConfigEntity companyConfigEntity = companyConfigModel.getByConfigAlias(CompanyConfigEnum.DINGTALK_GOODS_CODE.getAlias(), StringConstant.DEFAULT_CORPID);
            if (Objects.nonNull(companyConfigEntity)) {
                JSONObject goodsCodeObj = JsonHelperUtil.parseObject(companyConfigEntity.getConfigValue());
                loginInfoVO.setGoodsCode(goodsCodeObj.getString("goodsCode"));
                loginInfoVO.setToken(goodsCodeObj.getString("token"));
            }

            return loginInfoVO;
        }
        
        //移动端＋(一个试用套餐||非vip)+2022年2月21 00:00:00以后注册+未设置mobile+第一次登录
        boolean showSuiteKeyFlag = calShowSuiteKeyFlag(scenes, checkPackageVO, user);
        if (showSuiteKeyFlag) {
            loginInfoVO.setSuiteKey(ProBaseConfig.getSuiteKey());
        }
        // 是否免费版
        loginInfoVO.setIsFree(checkPackageVO.getIsFree());
        loginInfoVO.setIsTrail(checkPackageVO.getIsTrail());
        loginInfoVO.setMainCorpid(checkPackageVO.getMainCorpid());
        loginInfoVO.setIndustry(checkPackageVO.getIndustry());
        loginInfoVO.setScale(checkPackageVO.getScale());
        loginInfoVO.setFeeType(checkPackageVO.getFeeType());
        // 获取国际化标示
        String lang = getDingtalkI18nPack(corpid, userId);
        loginInfoVO.setLang(lang);

        boolean isOpenWorkflow = commonHelp.isOpenWorkFlow(corpid);
        loginInfoVO.setIsOpenWorkflow(isOpenWorkflow);

        return loginInfoVO;
    }

    private String getDingtalkI18nPack(String corpid, String userId) {
        String key = corpid + "_" + userId;
        String lang = paasRedisHelper.getValue(RedisPrefixConstant.PC_I18N_PACK, key);
        if (StringUtil.isEmpty(lang)) {
            UserConfigEntity userConfigEntity = userConfigModel.getByUserId(userId, corpid, UserConfigEnum.I18N_PACK.getAlias());
            if (userConfigEntity == null) {
                lang = "zh_CN";
            } else {
                lang = userConfigEntity.getConfigValue();
            }
            paasRedisHelper.setValue(RedisPrefixConstant.PC_I18N_PACK, key, lang, RedisTimeConstant.LONG_DURATION);
        }
        return lang;
    }

    @Override
    public GenerateNonceVO generateNonce(GenerateNonceDTO generateNonceDTO) throws XbbException {
        GenerateNonceVO generateNonceVO = new GenerateNonceVO();
        String corpid = generateNonceDTO.getCorpid();
        String userId = generateNonceDTO.getUserId();
        String nr = generateNonceDTO.getNr();

        UserEntity user = userModel.getByKey(userId, corpid);
        if(user == null) {
            throw new XbbException(SystemErrorCodeEnum.API_ERROR_100025);
        }

        try {
            String nrFromRedis = paasRedisHelper.getValue(RedisPrefixConstant.USER_INFO_DIGEST, user.getDingId());
            if(nrFromRedis == null || !nrFromRedis.equals(nr)) {
                LOG.error("nrFromRedis == " + nrFromRedis + "; nr == " + nr);
                throw new XbbException(SystemErrorCodeEnum.API_ERROR_100013);
            }
        } catch(Exception e) {
            LOG.error("从redis获取数据错误", e);
            throw new XbbException(SystemErrorCodeEnum.API_ERROR_100060);
        }

        /*
        计算并存储nonce 和 u
         */
        try {
            // 分隔符从一个下划线改为四个下划线
            String userId_corpId = userId + "____" + corpid;
            //URLEncode
            String userId_corpIdEnCode = URLEncodeUtils.encodeURL(userId_corpId);
            String nonce = StringUtil.getRandomPassword(4, 6);

            String userDES = DesUtil.encrypt(userId_corpIdEnCode, ConfigConstant.DES_DEFAULT_KEY);

            paasRedisHelper.setValuePermanent(RedisPrefixConstant.USER_AUTO_LOGIN_NONCE, userId_corpId, nonce);
            generateNonceVO.setNonce(nonce);
            generateNonceVO.setU(userDES);
        } catch (Exception e) {
            LOG.error("generateNonce逻辑执行出错", e);
            throw new XbbException(SystemErrorCodeEnum.API_ERROR_100001);
        }

        // web服务地址
        generateNonceVO.setWebFrontUrl(proBaseConfig.getWebFrontUrl());
        //话务中心是否启用
        Integer moduleType = null;
        generateNonceVO.setModuleType(moduleType);

        return generateNonceVO;
    }

    /**
     * 通过钉钉结果用code + corpid 确认当前登录的用户，用于钉钉登录逻辑
     * @param corpid 公司id
     * @param code 请求码
     * @return 当前登录的userId
     * @throws XbbException 业务异常
     */
    private String getUserIdFromDD(String corpid, String code) throws XbbException {
        String accessToken;
        JSONObject userIInfo = null;
        try {
            accessToken = authHelper.getAccessToken(corpid);
            userIInfo = UserHelper.getUserInfo(accessToken, code);
        } catch (OApiException e) {
            LOG.error("getUserIdFromDD出错: corpid = " + corpid, e);
        }

        if(userIInfo == null) {
            throw new XbbException(SystemErrorCodeEnum.API_ERROR_100024);
        }
        String userId = userIInfo.getString("userid");

        if(StringUtil.isEmpty(userId)) {
            throw new XbbException(SystemErrorCodeEnum.API_ERROR_100024);
        }
        return userId;
    }

    /**
     * 从钉钉后台管理页进入销帮帮登录
     * @param code 登录请求码
     * @return corpid + userId
     * @throws XbbException
     */
    private Map<String, String> getAdminUserFromDD(String code) throws XbbException {
        Map<String, String> ret = new HashMap<>(4);

        JSONObject userInfo = null;
        try {
            String ssoToken = authHelper.getSsoToken( );
            userInfo = UserHelper.getAgentUserInfo(ssoToken, code);
        } catch (OApiException e) {
            e.printStackTrace();
        }

        if(userInfo == null) {
            throw new XbbException(SystemErrorCodeEnum.API_ERROR_100062);
        }

        try{
            String corpid = userInfo.getJSONObject("corp_info").getString("corpid");
            String userId = userInfo.getJSONObject("user_info").getString("userid");
            ret.put("corpid", corpid);
            ret.put("userId", userId);

        }catch(Exception e){
            throw new XbbException(SystemErrorCodeEnum.API_ERROR_100062);
        }
        return ret;
    }
    
    /**
     * @param dingtalkBeforeConfigDTO
     * @throws XbbException 业务异常
     */
    @Override
    public DingtalkBeforeConfigVO beforeConfig(DingtalkBeforeConfigDTO dingtalkBeforeConfigDTO) throws XbbException {
        DingtalkBeforeConfigVO dingtalkBeforeConfigVO = new DingtalkBeforeConfigVO();
        String corpid = dingtalkBeforeConfigDTO.getCorpid();
        String code = dingtalkBeforeConfigDTO.getCode();
        LOG.info("beforeConfig+零食授权吗======"+code);
    
        String purchaseToken = dingtalkBeforeConfigDTO.getPurchaseToken();
        Long count = paasRedisHelper.getIncrNumByOne(RedisPrefixConstant.IS_TRIAL_PERSON, corpid, RedisTimeConstant.TINY_DURATION);
        Boolean isAuthorize = true;
        //trialFlag表示是否试用，1为试用，0或null为正常开通流程
        CompanyEntity companyEntity = companyModel.getNormalCompanyByKey(corpid);
        //表示这是在试用入口点入的，企业没有授权开通过
        if ( Objects.isNull(companyEntity) || Objects.isNull(code) ) {
            LOG.info("company:company公司没有授权成功，还在处理中..."+count);
            isAuthorize = false;
        }
        if ( isAuthorize ) {
            // 保险措施，填补缓存中的公司持久码
            companyService.getPermanentCode(corpid);
            String userId = getUserIdFromDD(corpid, code);
            /*
             * 从数据库中获取user
             * 若未null则说明该用户需要通过钉钉接口获取，并保存到我们系统的数据库中
             */
            UserEntity user = userModel.getByKey(userId, corpid);
            //表示这是在试用入口点入的，企业开通过，个人用户没有授权开通过
            if ( user == null ) {
                LOG.info("user：user用户没有授权成功，还在处理中..."+count);
                isAuthorize = false;
            }
        }
        dingtalkBeforeConfigVO.setIsAuthorize(isAuthorize);
        dingtalkBeforeConfigVO.setCount(count);
        return dingtalkBeforeConfigVO;
    }

    /**
     * 判断是否需要拉起钉钉个人信息授权弹窗，主要是为了获取用户的手机号
     * 钉钉个信信息授权：移动端＋(一个试用套餐||非vip)+2022年2月21 00:00:00以后注册+未设置mobile+第一次登录
     * @param scenes 访问场景，这里是为了判断是否为移动端登录
     * @param checkPackageVO 套餐判断实体
     * @param user 登录用户信息
     * @return 是否需要拉起钉钉个人信息授权弹窗
     */
    private boolean calShowSuiteKeyFlag(String scenes, CheckPackageVO checkPackageVO, UserEntity user) {
        String corpid = user.getCorpid();
        String userId = user.getUserId();
        // 钉钉个信信息授权：移动端＋第一次登录＋(一个试用套餐||非vip)
        boolean showSuiteKeyFlag = PlatFormEnum.DINGTALK.getValue().equals(scenes)
                && (checkPackageVO.getFeeNum() <= BasicConstant.ONE
                || checkPackageVO.getVipNum() < BasicConstant.ONE);
        // 符合条件，即符合移动端 + (一个试用套餐||非vip)
        if (showSuiteKeyFlag) {
            // 2022年2月21 00:00:00以前的不弹窗
            if (user.getAddTime() <= 1645372800) {
                showSuiteKeyFlag = false;
                // 已经填写好手机号，不弹窗
            } else if (StringUtil.isNotEmpty(user.getMobile())) {
                showSuiteKeyFlag = false;
            } else {
                String flag = paasRedisHelper.getValue(RedisPrefixConstant.MOBILE_COLLECT_FLAG, corpid + "____" + userId);
                // 缓存有值，说明已经不是第一次了，不弹窗
                if (StringUtil.isNotEmpty(flag) && Objects.equals("1", flag)) {
                    showSuiteKeyFlag = false;
                } else {
                    UserConfigEntity userConfig = userConfigModel.getByUserId(userId, corpid, UserConfigEnum.MOBILE_COLLECT_FLAG.getAlias());
                    // 配置中有值，说明已经不是第一次了，不弹窗
                    if (userConfig != null && Objects.equals("1", userConfig.getConfigValue())) {
                        showSuiteKeyFlag = false;
                    }
                    // 缓存无值，设置缓存
                    if (StringUtil.isEmpty(flag)) {
                        // 一天
                        paasRedisHelper.setValue(RedisPrefixConstant.MOBILE_COLLECT_FLAG, corpid + "____" + userId, "1", RedisTimeConstant.LONG_DURATION);
                        // 缓存有值时可以无视配置，除人为动作外（人为删除），不应该无配置；
                        // 缓存无值时，数据库也无值时，写入数据库
                        if (userConfig == null) {
                            UserConfigEntity newUserConfig = new UserConfigEntity(corpid, userId,
                                    "手机端是否有过手机号收集标志", "mobileCollectFlag", "1");
                            long now = DateTimeUtil.getInt();
                            newUserConfig.setAddTime(now);
                            newUserConfig.setUpdateTime(now);
                            userConfigModel.insert(newUserConfig);
                        }
                    }
                }
            }
        }
        return showSuiteKeyFlag;
    }

    @Override
    public CoolAppAccessStatusVO getCoolAppAccessStatus(CoolAppAccessStatusDTO coolAppAccessStatusDTO) {
        // 获取酷应用访问状态
        String accessUrl = Env.API_HOST + "/v1.0/appMarket/coolApps/accessions/statuses/query";
        String accessToken;
        try {
            accessToken = authHelper.getAccessToken(coolAppAccessStatusDTO.getCorpid());
        } catch (OApiException e) {
            LOG.info("获取accessToken失败", e);
            return new CoolAppAccessStatusVO();
        }
        // 参数校验
        if (Objects.isNull(coolAppAccessStatusDTO.getAuthCode())) {
            LOG.info("获取酷应用状态：授权码为空");
            return new CoolAppAccessStatusVO();
        }
        if (!Objects.equals(coolAppAccessStatusDTO.getCoolAppCode(), proBaseConfig.getCoolAppCode())) {
            LOG.info("获取酷应用状态：酷应用code无效");
            return new CoolAppAccessStatusVO();
        }
        if (Objects.isNull(coolAppAccessStatusDTO.getEncFieldBizCode())) {
            LOG.info("获取酷应用状态：群id为空");
            return new CoolAppAccessStatusVO();
        }

        JSONObject body = new JSONObject();
        String encFieldBizCode = coolAppAccessStatusDTO.getEncFieldBizCode();
        encFieldBizCode = URLEncodeUtils.decodeURL(encFieldBizCode);
        body.put("authCode", coolAppAccessStatusDTO.getAuthCode());
        body.put("coolAppCode", coolAppAccessStatusDTO.getCoolAppCode());
        body.put("encFieldBizCode", encFieldBizCode);
        JSONObject headers = new JSONObject();
        headers.put("Content-Type", "application/json");
        headers.put("x-acs-dingtalk-access-token", accessToken);
        String result;
        try {
            result = restTemplateHelper.post(accessUrl, body, headers, null);
        } catch (Exception e) {
            LOG.info("请求酷应用状态失败！", e);
            return new CoolAppAccessStatusVO();
        }

        LOG.info("result:" + result);
        return JsonHelperUtil.parseObject(result, CoolAppAccessStatusVO.class);
    }
}
